package me.sealer.ssm.controller;

import lombok.extern.slf4j.Slf4j;
import me.sealer.ssm.constant.Dict;
import me.sealer.ssm.constant.ErrorCodeConstant;
import me.sealer.ssm.constant.GiteeConstant;
import me.sealer.ssm.constant.GithubConstant;
import me.sealer.ssm.constant.LoginType;
import me.sealer.ssm.constant.Sex;
import me.sealer.ssm.constant.SsmConstant;
import me.sealer.ssm.constant.TokenConstant;
import me.sealer.ssm.constant.UserOrigin;
import me.sealer.ssm.exception.SsmException;
import me.sealer.ssm.model.User;
import me.sealer.ssm.service.UserService;
import me.sealer.ssm.shiro.authc.OpenAuth2Token;
import me.sealer.ssm.shiro.authc.PhoneToken;
import me.sealer.ssm.token.TokenManager;
import me.sealer.ssm.utils.MessageConverterUtil;
import me.sealer.ssm.utils.StringUtil;
import me.sealer.ssm.utils.UsernameUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.HostAuthenticationToken;
import org.apache.shiro.authc.RememberMeAuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Random;

/**
 * @author sealer
 * @date 2016/09/27
 */
@Slf4j
@Controller
public class SsmController {
    private static final String MY_GITHUB_CLIENT_ID = "8ffda36a06ccb0fe74a3";
    private static final String MY_GITHUB_CLIENT_SECRET = "853188b07a757e0c99205138bf56e94d44c41d74";

    private static final String MY_GITEE_CLIENT_ID = "96999b21ee76311576c8bf0e1441035d4b062c74b2158cdaaffa08ae8385189b";
    private static final String MY_GITEE_CLIENT_SECRET = "e4446ee18d1c0f71313f1b7d3453dc7bf07855f1a6e68d58874ad7c3a618d043";

    private final UserService userService;

    @Resource(name = "imageTokenManager")
    private TokenManager imageTokenManager;

    @Resource(name = "phoneTokenManager")
    private TokenManager phoneTokenManager;

    @Resource(name = "resubmitTokenManager")
    private TokenManager resubmitTokenManager;

    @Resource(name = "restTemplate")
    private RestTemplate restTemplate;

    public SsmController(UserService userService) {
        this.userService = userService;
    }

    @RequestMapping({"/preLogin", "/"})
    public String accountLoginPre() {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isRemembered() || subject.isAuthenticated()) {
            return "index";
        }
        return "login";
    }

    @RequestMapping("/quickLoginPre")
    public String quickLoginPre() {
        return "quickLogin";
    }

    @RequestMapping("/login")
    public String accountLogin(ModelMap modelMap, HttpServletRequest request) {
        String username = request.getParameter(Dict.USERNAME);
        LoginType loginType = LoginType.ACCOUNT;
        if (UsernameUtil.isPhoneNumber(username)) {
            loginType = LoginType.PHONE;
        }
        // 包含@符号， 改为email登录
        if (!UsernameUtil.notContainsAt(username)) {
            loginType = LoginType.EMAIL;
        }
        String resultString = commonLogin(loginType, modelMap, request);
        return resultString;
    }

    /**
     * common login logic
     *
     * @param loginType 登录类型
     * @param modelMap  视图model map
     * @param request   HttpServletRequest
     * @return 视图string
     */
    private String commonLogin(LoginType loginType, ModelMap modelMap, HttpServletRequest request) {
        String resultString;
        switch (loginType) {
            case ACCOUNT:
                String username = request.getParameter(Dict.USERNAME);
                resultString = accountLoginInner(modelMap, request, username);
                break;
            case EMAIL:
                resultString = emailLoginInner(modelMap, request);
                break;
            case PHONE:
                resultString = phoneLoginInner(modelMap, request);
                break;
            case QUICK:
                resultString = quickLoginInner(modelMap, request);
                break;
            case GITHUB:
                resultString = githubLoginInner(modelMap, request);
                break;
            case GITEE:
                resultString = giteeLoginInner(modelMap, request);
                break;
            default:
                throw new SsmException(ErrorCodeConstant.USER_LOGIN_TYPE_ERROR);
        }

        return resultString;
    }

    private String githubLoginInner(ModelMap modelMap, HttpServletRequest request) {
        String code = request.getParameter(GithubConstant.CODE);

        Map githubAccessTokenRequestMap = new HashMap(8);
        githubAccessTokenRequestMap.put(GithubConstant.CLIENT_ID, MY_GITHUB_CLIENT_ID);
        githubAccessTokenRequestMap.put(GithubConstant.CLIENT_SECRET, MY_GITHUB_CLIENT_SECRET);
        githubAccessTokenRequestMap.put(GithubConstant.CODE, code);

        ResponseEntity<Map> githubAccessTokenResponseEntity = restTemplate.postForEntity(GithubConstant.GITHUB_ACCESS_TOKEN_URL, githubAccessTokenRequestMap, Map.class);
        Map githubAccessTokenResponseMap = githubAccessTokenResponseEntity.getBody();

        String accessToken = (String) githubAccessTokenResponseMap.get(GithubConstant.ACCESS_TOKEN);

        String githubUserRequestUrl = GithubConstant.GITHUB_USER_URL
                + "?"
                + GithubConstant.ACCESS_TOKEN
                + "="
                + accessToken;

        ResponseEntity<Map> githubUserResponseEntity = restTemplate.getForEntity(githubUserRequestUrl, Map.class);
        Map userResponseEntityMap = githubUserResponseEntity.getBody();
        String username = (String) userResponseEntityMap.get(GithubConstant.LOGIN);
        String userAvatarUrl = (String) userResponseEntityMap.get(GithubConstant.AVATAR_URL);
        User user = userService.findUserByUsername(username);

        boolean neededCreate = false;
        if (user != null) {
            // 用户名存在， 且未从github登录过， 则再生成一个唯一用户名
            if (!Objects.equals(UserOrigin.GITHUB, user.getOrigin())) {
                String usernameSuffix = "";
                // 保证用户名不存在
                while (user != null) {
                    usernameSuffix = "_" + new Random().nextInt();
                    user = userService.findUserByUsername(username + usernameSuffix);
                }
                username += usernameSuffix;

                neededCreate = true;
            }
        } else {
            neededCreate = true;
        }

        if (neededCreate) {
            // 正常注册流程已经限制不能使用纯数字， 所以这里使用手机号作为用户名。 约定。
            // 密码初始值为 ssm_123456, 快捷注册成功后发送短信通知尽快修改。约定。

            User newUser = User.builder()
                    // 默认用户名
                    .username(username)
                    // 默认密码 ssm_123456
                    .password("ssm_123456")
                    // 默认性别保密
                    .sex(Sex.UNKNOWN)
                    .origin(UserOrigin.GITHUB)
                    .avatarUrl(userAvatarUrl)
                    .build();

            boolean userCreateSuccess = userService.createUser(newUser);
            if (!userCreateSuccess) {
                throw new SsmException(ErrorCodeConstant.USER_LOGIN_FAIL);
            }
        }

        User responseUser = userService.findUserByUsername(username);
        Subject subject = SecurityUtils.getSubject();

        OpenAuth2Token openAuth2Token = new OpenAuth2Token(username, username);

        boolean isPermitted = shiroLoginSuccessfully(subject, openAuth2Token);

        if (isPermitted) {
            modelMap.put(SsmConstant.USER_AVATAR_URL, responseUser.getAvatarUrl());
            return "redirect:/";
        } else {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.USER_LOGIN_FAIL);
            modelMap.addAttribute(SsmConstant.RESULT_MESSAGE, message);
            return "login";
        }
    }

    private String giteeLoginInner(ModelMap modelMap, HttpServletRequest request) {
        String code = request.getParameter(GiteeConstant.CODE);

        MultiValueMap<String, String> giteeAccessTokenRequestMap = new LinkedMultiValueMap<>(8);
        giteeAccessTokenRequestMap.add(GiteeConstant.CLIENT_ID, MY_GITEE_CLIENT_ID);
        giteeAccessTokenRequestMap.add(GiteeConstant.CLIENT_SECRET, MY_GITEE_CLIENT_SECRET);
        giteeAccessTokenRequestMap.add(GiteeConstant.CODE, code);
        giteeAccessTokenRequestMap.add(GiteeConstant.REDIRECT_URI, "http://localhost:8888/ssm/callback/gitee");
        // oauth2授权码模式
        giteeAccessTokenRequestMap.add(GiteeConstant.GRANT_TYPE, "authorization_code");

        //headers
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36");

        //HttpEntity
        HttpEntity<MultiValueMap> accessTokenRequestEntity = new HttpEntity<>(giteeAccessTokenRequestMap, requestHeaders);

        ResponseEntity<Map> giteeAccessTokenResponseEntity = restTemplate.postForEntity(GiteeConstant.GITEE_ACCESS_TOKEN_URL, accessTokenRequestEntity, Map.class);
        Map giteeAccessTokenResponseMap = giteeAccessTokenResponseEntity.getBody();

        String accessToken = (String) giteeAccessTokenResponseMap.get(GiteeConstant.ACCESS_TOKEN);

        String giteeUserRequestUrl = GiteeConstant.GITEE_USER_URL
                + "?"
                + GiteeConstant.ACCESS_TOKEN
                + "="
                + accessToken;

        //HttpEntity
        HttpEntity<MultiValueMap> userRequestEntity = new HttpEntity<>(null, requestHeaders);

        ResponseEntity<Map> giteeUserResponseEntity = restTemplate.exchange(giteeUserRequestUrl, HttpMethod.GET, userRequestEntity, Map.class);
        Map userResponseEntityMap = giteeUserResponseEntity.getBody();
        String username = (String) userResponseEntityMap.get(GiteeConstant.LOGIN);
        String userAvatarUrl = (String) userResponseEntityMap.get(GiteeConstant.AVATAR_URL);
        User user = userService.findUserByUsername(username);

        boolean neededCreate = false;
        if (user != null) {
            // 用户名存在， 且未从gitee登录过， 则再生成一个唯一用户名
            if (!Objects.equals(UserOrigin.GITEE, user.getOrigin())) {
                String usernameSuffix = "";
                // 保证用户名不存在
                while (user != null) {
                    usernameSuffix = "_" + new Random().nextInt();
                    user = userService.findUserByUsername(username + usernameSuffix);
                }
                username += usernameSuffix;

                neededCreate = true;
            }
        } else {
            neededCreate = true;
        }

        if (neededCreate) {
            // 正常注册流程已经限制不能使用纯数字， 所以这里使用手机号作为用户名。 约定。
            // 密码初始值为 ssm_123456, 快捷注册成功后发送短信通知尽快修改。约定。

            User newUser = User.builder()
                    // 默认用户名
                    .username(username)
                    // 默认密码 ssm_123456
                    .password("ssm_123456")
                    // 默认性别保密
                    .sex(Sex.UNKNOWN)
                    .origin(UserOrigin.GITEE)
                    .avatarUrl(userAvatarUrl)
                    .build();

            boolean userCreateSuccess = userService.createUser(newUser);
            if (!userCreateSuccess) {
                throw new SsmException(ErrorCodeConstant.USER_LOGIN_FAIL);
            }
        }

        User responseUser = userService.findUserByUsername(username);
        Subject subject = SecurityUtils.getSubject();

        OpenAuth2Token openAuth2Token = new OpenAuth2Token(username, username);

        boolean isPermitted = shiroLoginSuccessfully(subject, openAuth2Token);

        if (isPermitted) {
            modelMap.put(SsmConstant.USER_AVATAR_URL, responseUser.getAvatarUrl());
            return "redirect:/";
        } else {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.USER_LOGIN_FAIL);
            modelMap.addAttribute(SsmConstant.RESULT_MESSAGE, message);
            return "login";
        }
    }

    @RequestMapping("/quickLogin")
    public String quickLogin(ModelMap modelMap, HttpServletRequest request) {
        LoginType loginType = LoginType.QUICK;
        String resultString = commonLogin(loginType, modelMap, request);
        return resultString;
    }

    @RequestMapping("/callback/github")
    public String githubLogin(ModelMap modelMap, HttpServletRequest request) {
        LoginType loginType = LoginType.GITHUB;
        String resultString = commonLogin(loginType, modelMap, request);
        return resultString;
    }

    @RequestMapping("/callback/gitee")
    public String giteeLogin(ModelMap modelMap, HttpServletRequest request) {
        LoginType loginType = LoginType.GITEE;
        String resultString = commonLogin(loginType, modelMap, request);
        return resultString;
    }

    @RequestMapping("/logout")
    public String logout(ModelMap modelMap, HttpServletRequest request) {
        Subject subject = SecurityUtils.getSubject();
        // 退出
        subject.logout();
        modelMap.addAttribute(SsmConstant.RESULT_MESSAGE, MessageConverterUtil.getMessage(request, ErrorCodeConstant.SYSTEM_EXIT_SAFELY));
        return "login";
    }

    @RequestMapping("/registerPre")
    public String registerPre() {
        return "register";
    }

    /**
     * 注册不允许使用纯数字和@邮箱符号
     *
     * @param request
     * @return
     */
    @RequestMapping("/register")
    public ModelAndView register(HttpServletRequest request) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("registerSuccess");

        String username = request.getParameter(Dict.USERNAME);

        boolean isPureNumber = UsernameUtil.isNumber(username);
        if (isPureNumber) {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.USER_USERNAME_CANNOT_BE_PURE_NUMBER);
            modelAndView.addObject(SsmConstant.RESULT_MESSAGE, message);
            modelAndView.setViewName("register");
            return modelAndView;
        }

        boolean notContainsAt = UsernameUtil.notContainsAt(username);
        if (!notContainsAt) {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.USER_USERNAME_CANNOT_CONTAINS_AT);
            modelAndView.addObject(SsmConstant.RESULT_MESSAGE, message);
            modelAndView.setViewName("register");
            return modelAndView;
        }

        String password = request.getParameter(Dict.PASSWORD);
        String confirmPassword = request.getParameter(Dict.CONFIRM_PASSWORD);
        String birthdayStr = request.getParameter(Dict.BIRTHDAY);
        Date birthday;
        try {
            birthday = new SimpleDateFormat(Dict.YYYY_MM_DD).parse(birthdayStr);
        } catch (ParseException e) {
            throw new SsmException(e);
        }
        String address = request.getParameter(Dict.ADDRESS);
        String sex = request.getParameter(Dict.SEX);

        User user = userService.findUserByUsername(username);

        if (user != null) {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.USER_USER_ALREADY_EXISTS, new Object[]{username});
            modelAndView.addObject(SsmConstant.RESULT_MESSAGE, message);
            modelAndView.setViewName("register");
            return modelAndView;
        }

        // 判断二次输入的新确认密码是否与新设置的密码相同
        boolean passNewConfirmEquals = StringUtil.equals(password, confirmPassword);
        if (!passNewConfirmEquals) {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.USER_PASSWORD_CONFIRM_WRONG);
            modelAndView.addObject(SsmConstant.RESULT_MESSAGE, message);
            modelAndView.setViewName("register");
            return modelAndView;
        }

        user = User.builder()
                .username(username)
                .password(password)
                .birthday(birthday)
                .address(address)
                .sex(sex)
                .build();

        boolean userCreateSuccess = userService.createUser(user);

        if (!userCreateSuccess) {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.USER_REGISTER_FAIL);
            modelAndView.addObject(SsmConstant.RESULT_MESSAGE, message);
            modelAndView.setViewName("register");
        }
        return modelAndView;
    }

    /**
     * 注册用户登录：用户名+密码
     *
     * @param modelMap
     * @param request
     * @return
     */
    private String accountLoginInner(ModelMap modelMap, HttpServletRequest request, String username) {
        String rememberMeStr = request.getParameter(Dict.REMEMBER_ME);
        Boolean rememberMe = Boolean.valueOf(rememberMeStr);

        String password = request.getParameter(Dict.PASSWORD);

        // 校验图形验证码
        int verifyResultCode = imageTokenManager.verifyToken(request);
        if (TokenConstant.TOKEN_SUCCESS != verifyResultCode) {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.TOKEN_IMAGE_TOKEN_ERROR);
            modelMap.addAttribute(SsmConstant.RESULT_MESSAGE, message);
            return "login";
        }

        Subject subject = SecurityUtils.getSubject();

        if (subject.isRemembered() || subject.isAuthenticated()) {
            return "index";
        }

        boolean isPermitted = true;
        if (StringUtil.isNullOrEmpty(username) || StringUtil.isNullOrEmpty(password)) {
            isPermitted = false;
        }
        if (isPermitted) {
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            if (rememberMe != null) {
                token.setRememberMe(rememberMe);
            }
            isPermitted = shiroLoginSuccessfully(subject, token);
        }
        if (isPermitted) {
            return "index";
        } else {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.USER_LOGIN_FAIL);
            modelMap.addAttribute(SsmConstant.RESULT_MESSAGE, message);
            return "login";
        }
    }

    /**
     * shiro login
     *
     * @param subject shiro subject
     * @param token   shiro token
     * @return login success or fail
     */
    private <T extends HostAuthenticationToken & RememberMeAuthenticationToken> boolean shiroLoginSuccessfully(Subject subject, T token) {
        boolean loginSuccessfully = true;
        try {
            subject.login(token);
            if (log.isDebugEnabled()) {
                Session session = subject.getSession(false);
                log.debug("session id is : " + session.getId());
                session.getAttributeKeys().forEach(key ->
                        log.debug(key + " : " + session.getAttribute(key)));
            }
        } catch (Exception e) {
            log.error(e.toString());
            loginSuccessfully = false;
        }
        return loginSuccessfully;
    }

    /**
     * 手机验证码快捷登录
     *
     * @param modelMap
     * @param request
     * @return
     */
    private String quickLoginInner(ModelMap modelMap, HttpServletRequest request) {
        int verifyResultCode = imageTokenManager.verifyToken(request);
        if (TokenConstant.TOKEN_SUCCESS != verifyResultCode) {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.TOKEN_IMAGE_TOKEN_ERROR);
            modelMap.addAttribute(SsmConstant.RESULT_MESSAGE, message);
            return "quickLogin";
        }

        verifyResultCode = resubmitTokenManager.verifyToken(request);
        if (TokenConstant.TOKEN_SUCCESS != verifyResultCode) {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.TOKEN_RESUBMIT_TOKEN_ERROR);
            modelMap.addAttribute(SsmConstant.RESULT_MESSAGE, message);
            return "quickLogin";
        }

        verifyResultCode = phoneTokenManager.verifyToken(request);
        if (TokenConstant.TOKEN_SUCCESS != verifyResultCode) {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.TOKEN_PHONE_TOKEN_ERROR);
            modelMap.addAttribute(SsmConstant.RESULT_MESSAGE, message);
            return "quickLogin";
        }

        String phoneNumber = request.getParameter(Dict.PHONE_NUMBER);

        User user = userService.findUserByPhoneNumber(phoneNumber);

        // 正常注册流程已经限制不能使用纯数字， 所以这里使用手机号作为用户名。 约定。
        // 密码初始值为 ssm_123456, 快捷注册成功后发送短信通知尽快修改。约定。
        if (user == null) {
            User newUser = User.builder()
                    // 默认用户名
                    .username(phoneNumber)
                    // 默认密码 ssm_123456
                    .password("ssm_123456")
                    .phoneNumber(phoneNumber)
                    // 默认性别保密
                    .sex(Sex.UNKNOWN)
                    .build();

            boolean userCreateSuccess = userService.createUser(newUser);
            if (!userCreateSuccess) {
                throw new SsmException(ErrorCodeConstant.USER_LOGIN_FAIL);
            }

            // TODO 短信通知用户临时密码及临时用户名
            user = userService.findUserByPhoneNumber(phoneNumber);
        }

        String username = user.getUsername();

        Subject subject = SecurityUtils.getSubject();

        PhoneToken phoneToken = new PhoneToken(username, phoneNumber);

        boolean isPermitted = shiroLoginSuccessfully(subject, phoneToken);

        if (isPermitted) {
            return "index";
        } else {
            String message = MessageConverterUtil.getMessage(request, ErrorCodeConstant.USER_LOGIN_FAIL);
            modelMap.addAttribute(SsmConstant.RESULT_MESSAGE, message);
            return "quickLogin";
        }
    }

    private String emailLoginInner(ModelMap modelMap, HttpServletRequest request) {
        String email = request.getParameter(Dict.USERNAME);
        String username = null;
        User user = userService.findUserByEmail(email);
        if (user != null) {
            username = user.getUsername();
        }
        return accountLoginInner(modelMap, request, username);
    }

    private String phoneLoginInner(ModelMap modelMap, HttpServletRequest request) {
        String phone = request.getParameter(Dict.USERNAME);
        String username = null;
        User user = userService.findUserByPhoneNumber(phone);
        if (user != null) {
            username = user.getUsername();
        }
        return accountLoginInner(modelMap, request, username);
    }
}